/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.jsr223;

import axil.api.Axil;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static axil.framework.Functions.unreachable;


/**
 * An Axil implementation of the JSR-223 ScriptEngineFactory.
 */
public class AxilEngineFactory implements ScriptEngineFactory {
	private enum Param {
		ENGINE(ScriptEngine.ENGINE),
		ENGINE_VERSION(ScriptEngine.ENGINE_VERSION),
		NAME(ScriptEngine.NAME),
		LANGUAGE(ScriptEngine.LANGUAGE),
		LANGUAGE_VERSION(ScriptEngine.LANGUAGE_VERSION),
		THREADING("THREADING");

		private String key;

		private Param(String key)	{
			this.key = key;
		}
		public String key() 		{
			return key;
		}
	}
	private static final Map<String,Param> params = new HashMap<String, Param>();
	static {
		params.put(Param.ENGINE.key(), Param.ENGINE);
		params.put(Param.ENGINE_VERSION.key(), Param.ENGINE_VERSION);
		params.put(Param.NAME.key(), Param.NAME);
		params.put(Param.LANGUAGE.key(), Param.LANGUAGE);
		params.put(Param.LANGUAGE_VERSION.key(), Param.LANGUAGE_VERSION);
	}

	private static final List<String> aliases = new ArrayList<String>();
	static {
		aliases.add("Axil");
		aliases.add("AXIL");
		aliases.add("axil");
	}

	private Axil axil;


	public AxilEngineFactory() {
		this.axil = Axil.instance();
	}


	/**
	 * Returns the full  name of the ScriptEngine.  For Axil, the return value
	 * is always "Axil Script Engine".
	 *
	 * @return
	 * 	Returns the name of the engine implementation.
	 */
	public String getEngineName() {
		return "Axil Script Engine";
	}


	/**
	 * Returns the version of the ScriptEngine.
	 *
	 * @return
	 * 	Returns the ScriptEngine implementation version.
	 */
	public String getEngineVersion() {
		return axil.release();
	}


	/**
	 * Returns an immutable list of filename extensions, which generally
	 * identify scripts written in the language supported by this ScriptEngine.
	 * The array is used by the ScriptEngineManager to implement its
	 * getEngineByExtension() method.
	 *
	 * @return
	 * 	Returns the list of extensions.
	 */
	public List<String> getExtensions() {
		return null;
	}


	/**
	 * Returns an immutable list of MIME types, associated with scripts that
	 * can be executed by the engine. The list is used by the ScriptEngineManager
	 * class to implement its getEngineByMimetype() method.
	 *
	 * @return
	 * 	Returns the list of mime types.
	 */
	public List<String> getMimeTypes() {
		return null;
	}


	/**
	 * Returns an immutable list of  short names for the ScriptEngine, which
	 * may be used to identify the ScriptEngine by the ScriptEngineManager.
	 * For Axil, the list includes "Axil", "AXIL" and "axil".
	 */
	public List<String> getNames() {
		return aliases;
	}


	/**
	 * Returns the name of the scripting language supported by this ScriptEngine.
	 * The returned value is always "Axil".
	 *
	 * @return
	 * 	Returns the name of the supported language.
	 */
	public String getLanguageName() {
		return "Axil";
	}


	/**
	 * Returns the version of the scripting language supported by this
	 * ScriptEngine.
	 * @return The version of the supported language.
	 */
	public String getLanguageVersion() {
		return axil.language();
	}


	/**
	 * Returns the value of an attribute whose meaning may be
	 * implementation-specific. Keys for which the value is defined in all
	 * implementations are:
	 * <ul>
	 * 		<li>ScriptEngine.ENGINE</li>
	 * 		<li>ScriptEngine.ENGINE_VERSION</li>
	 * 		<li>ScriptEngine.NAME</li>
	 * 		<li>ScriptEngine.LANGUAGE</li>
	 * 		<li>ScriptEngine.LANGUAGE_VERSION</li>
	 * </ul>
	 * The values for these keys are the Strings returned by getEngineName,
	 * getEngineVersion, getName, getLanguageName and getLanguageVersion
	 * respectively.
	 *
	 * @param key
	 * 	The name of the parameter. JSR-223 is so poorly designed that they refer
	 * 	to this as a key, parameter ans attribute all in the same paragraph.
	 *
	 * @return
	 * 	The value for the given parameter. Returns null if no value is assigned
	 * 	to the key.
	 */
	public Object getParameter(String key) {
		Param p = params.get(key);
		if (p == null) {
			return null;
		}
		switch(p) {
		case ENGINE:
			return this.getEngineName();

		case ENGINE_VERSION:
			return this.getEngineVersion();

		case LANGUAGE:
			return this.getLanguageName();

		case LANGUAGE_VERSION:
			return this.getLanguageVersion();

		case NAME:
			return this.getNames().get(0);

		case THREADING:
			/*
			 * Per JSR-223, the engine implementation is internally
			 * thread-safe and scripts may execute concurrently and effects
			 * of script execution on one thread are not visible to scripts
			 * on other threads. The engine maintains independent values for
			 * symbols in scripts executing on different threads. Finally,
			 * script executions do not alter the mappings in the Bindings
			 * which is the engine scope of the ScriptEngine. In particular,
			 * the keys in the Bindings and their associated values are the
			 * same before and after the execution of the script.
			 */
			return "STATELESS";

		default:
			throw unreachable();
		}
	}


	/**
	 * Returns a String which can be used to invoke a method of a Java object
	 * using the syntax of the supported scripting language. Since Axil is
	 * neither object oriented nor allows access to arbitrary elements of a
	 * Java object for safety reasons, this method does not guarantee to return
	 * a string that will execute without error or that will even compile. It
	 * only guarantees to return code that cannot alter or damage the host
	 * application.
	 *
	 * @param object
	 * 	The name representing the object whose method is to be invoked. The
	 * 	name is the one used to create bindings using the put method of
	 * 	ScriptEngine, the put method of an ENGINE_SCOPE Bindings,or the
	 * 	setAttribute() method of ScriptContext. The identifier used in scripts
	 * 	may be a decorated form of the specified one.
	 *
	 * @param method
	 * 	The name of the method to invoke. For Axil, this is interpreted as
	 * 	the member of the object to be accessed.
	 *
	 * @param args
	 * 	The names of the arguments in the method call. Axil only supports zero
	 * 	argument methods.
	 *
	 * @return
	 * 	Returns the string used to invoke the method in the syntax of the
	 * 	scripting language.
	 */
	public String getMethodCallSyntax(String object, String method,
	                                  String... args) {
		/*
		 * This is a pretty lame implementation. It can be seriously improved,
		 * but I doubt that anyone actually uses this nonsense. If you want a
		 * better implementation, please file a request at the Axil project
		 * site.
		 */
		if (args.length > 0) {
			return getOutputStatement("Cannot invoke that.");
		}
		return '`' + object + '.' + method + '`';
	}


	/**
	 * Returns a String that can be used as a statement to display the specified
	 * String  using the syntax of the supported scripting language.
	 *
	 * @param expression
	 * 	The String to be displayed by the returned statement.
	 *
	 * @return
	 * 	The string used to display the String in the syntax of the scripting
	 * 	language.
	 */
	public String getOutputStatement(String expression) {
		return "print(" + expression + ')';
	}


	/**
	 * Returns A valid scripting language executable program with given
	 * statements. Since Axil is expression-oriented, not statement oriented,
	 * the program is assembled into a script by simply concatenating the
	 * "statements" together.
	 *
	 * @param statements
	 *  The statements to be executed. May be return values of calls to the
	 *  getMethodCallSyntax() and getOutputStatement methods.
	 *
	 * @return
	 * 	Returns the program.
	 */
	public String getProgram(String... statements) {
		StringBuilder b = new StringBuilder();
		for (String s : statements) {
			b.append(s);
		}
		return b.toString();
	}


	/**
	 * Returns an instance of the ScriptEngine associated with this
	 * ScriptEngineFactory. A new ScriptEngine is generally returned, but
	 * implementations may pool, share or reuse engines.
	 *
	 * @return
	 * 	Returns a new ScriptEngine instance.
	 */
	public ScriptEngine getScriptEngine() {
		return new AxilScriptEngine();
	}
}
